﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2021-2025 LiLong                                              **
**  This file is part of CommonUnits.                                           **
**                                                                              **
**  CommonUnits is free software: you can redistribute it and/or modify         **
**  it under the terms of the GNU Lesser General Public License as published by **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  CommonUnits is distributed in the hope that it will be useful,              **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU Lesser General Public License for more details.                         **
**                                                                              **
**  You should have received a copy of the GNU Lesser General Public License    **
**  along with CommonUnits.  If not, see <https://www.gnu.org/licenses/>.       **
**********************************************************************************/
#pragma once

#include "Adl.h"
#include "StandardUnit.h"

#include <compare>
#include <concepts>
#include <ratio>
#include <type_traits>

namespace CU
{
template<typename _Type, UnitType _Unit>
requires std::is_arithmetic_v<_Type>
class CommonUnits;
template<typename T>
constexpr bool _isCommonUnits = std::false_type {};
template<typename T, UnitType U>
constexpr bool _isCommonUnits<CommonUnits<T, U>> = std::true_type {};
template<typename T>
constexpr bool _isCommonUnitsSubclass = std::false_type {};
template<template<typename...> typename _Template, typename T, UnitType U>
constexpr bool _isCommonUnitsSubclass<_Template<T, U>> = std::is_base_of_v<CommonUnits<T, U>, _Template<T, U>>;
template<typename T>
concept CommonUnitsType = _isCommonUnits<T> || _isCommonUnitsSubclass<T>;

template<typename T, typename U>
concept CanCast = requires {
    isSeriesUnit<typename T::Unit, typename U::Unit>;
    CommonUnitsType<T>&& CommonUnitsType<U>;
};

template<CommonUnitsType Dest, typename Type, typename Unit>
requires CanCast<Dest, CommonUnits<Type, Unit>>
[[nodiscard]] constexpr Dest commonUnitsCast(const CommonUnits<Type, Unit>& _Dur) noexcept;

template<typename _Type, UnitType _Unit>
requires std::is_arithmetic_v<_Type>
class CommonUnits
{
public:
    using Type  = _Type;
    using Ratio = _Unit::Ratio;
    using Unit  = _Unit;

    constexpr CommonUnits()  = default;
    constexpr ~CommonUnits() = default;
    explicit constexpr CommonUnits(Type value) : m_value(value) {}
    template<typename T, UnitType U>
    requires std::is_arithmetic_v<_Type>
    constexpr CommonUnits(const CommonUnits<T, U>& value)
    {
        operator=(value);
    }

    [[nodiscard]] constexpr Type count() const { return m_value; }
    [[nodiscard]] static constexpr auto unit() { return Unit::unit; }

    template<typename T, typename U>
    [[nodiscard]] constexpr CommonUnits operator-(const CommonUnits<T, U>& other) const noexcept
    {
        return CommonUnits(m_value - commonUnitsCast<CommonUnits>(other).count());
    }

    template<typename T, typename U>
    [[nodiscard]] constexpr CommonUnits operator+(const CommonUnits<T, U>& other) const noexcept
    {
        return CommonUnits(m_value + commonUnitsCast<CommonUnits>(other).count());
    }

    [[nodiscard]] constexpr CommonUnits operator-() const noexcept { return CommonUnits(-m_value); }

    [[nodiscard]] constexpr CommonUnits operator+() const noexcept { return CommonUnits(m_value); }

    template<typename T>
    requires std::is_arithmetic_v<T>
    [[nodiscard]] constexpr CommonUnits operator*(const T& value) const noexcept
    {
        return CommonUnits(m_value * value);
    }

    template<typename T>
    requires std::is_arithmetic_v<T>
    friend [[nodiscard]] constexpr CommonUnits operator*(const T& value, const CommonUnits& other)
    {
        return other * value;
    }

    template<typename T>
    requires std::is_arithmetic_v<T>
    [[nodiscard]] constexpr CommonUnits operator/(const T& value) const noexcept
    {
        return CommonUnits(m_value / value);
    }

    template<typename T, typename U>
    [[nodiscard]] constexpr Type operator/(const CommonUnits<T, U>& other) const noexcept
    {
        return m_value / commonUnitsCast<CommonUnits>(other).count();
    }

    template<typename T, typename U>
    constexpr CommonUnits& operator+=(const CommonUnits<T, U>& other) noexcept
    {
        m_value += commonUnitsCast<CommonUnits>(other).count();
        return *this;
    }

    template<typename T, typename U>
    constexpr CommonUnits& operator-=(const CommonUnits<T, U>& other) noexcept
    {
        m_value -= commonUnitsCast<CommonUnits>(other).count();
        return *this;
    }

    template<typename T>
    requires std::is_arithmetic_v<T>
    constexpr CommonUnits& operator*=(const T& value) noexcept
    {
        m_value *= value;
        return *this;
    }

    template<typename T>
    requires std::is_arithmetic_v<T>
    constexpr CommonUnits& operator/=(const T& value) noexcept
    {
        m_value /= value;
        return *this;
    }

    template<typename T, typename U>
    constexpr CommonUnits& operator=(const CommonUnits<T, U>& other) noexcept
    {
        m_value = commonUnitsCast<CommonUnits>(other).count();
        return *this;
    }

    template<typename T, typename U>
    [[nodiscard]] constexpr std::strong_ordering operator<=>(const CommonUnits<T, U>& other) const noexcept
    {
        using _Ty = std::common_type_t<T, Type>;
        if constexpr (std::is_floating_point_v<_Ty>)
        {
            auto val = m_value - commonUnitsCast<CommonUnits>(other).count();
            if (std::abs(val) < std::numeric_limits<_Ty>::min())
                return std::strong_ordering::equal;
            else
                return val > 0 ? std::strong_ordering::greater : std::strong_ordering::less;
        }
        else
            return m_value <=> commonUnitsCast<CommonUnits>(other).count();
    }

    template<typename T, typename U>
    [[nodiscard]] constexpr bool operator==(const CommonUnits<T, U>& other) const noexcept
    {
        return *this <=> other == std::strong_ordering::equal;
    }

    template<typename T>
    [[nodiscard]] inline explicit operator T() const
    {
        T dest;
        fromUnit(*this, dest);
        return dest;
    }

protected:
    Type m_value { Type() };
};

template<typename CharT, typename Traits, typename Type, typename Unit>
inline std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const CommonUnits<Type, Unit>& unit)
{
    return os << unit.count() << unit.unit();
}

template<CommonUnitsType Dest, typename Type, typename Unit>
requires CanCast<Dest, CommonUnits<Type, Unit>>
[[nodiscard]] constexpr Dest commonUnitsCast(const CommonUnits<Type, Unit>& _Dur) noexcept
{
    using _CF    = std::ratio_divide<typename Unit::Ratio, typename Dest::Ratio>;
    using _ToRep = typename Dest::Type;
    using _CR    = std::common_type_t<_ToRep, Type, intmax_t>;

    constexpr bool numIsOne = _CF::num == 1;
    constexpr bool denIsOne = _CF::den == 1;

    if constexpr (denIsOne)
    {
        if constexpr (numIsOne)
        {
            return static_cast<Dest>(static_cast<_ToRep>(_Dur.count()));
        }
        else
        {
            return static_cast<Dest>(static_cast<_ToRep>(static_cast<_CR>(_Dur.count()) * static_cast<_CR>(_CF::num)));
        }
    }
    else
    {
        if constexpr (numIsOne)
        {
            return static_cast<Dest>(static_cast<_ToRep>(static_cast<_CR>(_Dur.count()) / static_cast<_CR>(_CF::den)));
        }
        else
        {
            return static_cast<Dest>(
                static_cast<_ToRep>(static_cast<_CR>(_Dur.count()) * static_cast<_CR>(_CF::num) / static_cast<_CR>(_CF::den)));
        }
    }
}

template<CommonUnitsType T, typename _CharT>
struct std::formatter<T, _CharT> : std::formatter<double, _CharT>
{
    template<typename _FormatContext>
    typename _FormatContext::iterator format(const T& v, _FormatContext& format_context) const
    {
        typename _FormatContext::iterator Ite = std::formatter<double, _CharT>::format(v.count(), format_context);
        Ite                                   = std::formatter<const char*, _CharT>().format(v.unit().c_str(), format_context);
        return Ite;
    }
};
} // namespace CU
